1 重要的写前面

1.1 解决办法

修改$FEKIT_HOME/lib/util.js,性能能提升N倍。

exists: function(path) {
  //return _fileExistsWithCaseSync(path);
  
  if (fs.existsSync) {
    return fs.existsSync(path);
  }
  if (syspath.existsSync) {
    return syspath.existsSync(path);
  }
  
}

1.2 效果

pack、min 过程减少N分钟

1.3 原因分析

说实话没看懂到底要干啥,github上抠代码,发现 是2014年12月份提交的, 当时的提交日志是“开发时require区分大小写”。

发布时间是v0.2.88之后 0.2.99之前

看完又是懵逼了,代码里面没有看到大小写,整个工程中也就这一个文件用到了这个方法。

技术原因倒也简单,递归的效率太低(指数增长),这里一次能搞定的东东,搞了很多次,自然慢了。

找一个工程 打印exists调用次数 618958,写一段代码测试下 1000次执行的差异,可以看出这个工程可以提升近十分钟:

################
修改后: 50
################
修改前N级目录: 607
################
修改前N+2级目录: 785
################
修改前N+4级目录: 976

代码如下:

var fs = require("fs");
var syspath = require('path');

var _fileExistsWithCaseSync = function (filepath) {
    //console.log("pid:", process.pid, '--', filepath, + new Date())
    var dir, filenames;
    if (!fs.existsSync(filepath)) {
        return false;
    }
    dir = syspath.dirname(filepath);
    if (dir === '/' || dir === '.' || (/^\w:\\$/.test(dir))) {
        //console.log("pid:", process.pid, '--', filepath, + new Date())
        return true;
    }
    filenames = fs.readdirSync(dir);
    if (filenames.indexOf(syspath.basename(filepath)) === -1) {
        return false;
    }
    return _fileExistsWithCaseSync(dir);
};

var exists = function (path) {
    //  return _fileExistsWithCaseSync(path);
    //console.log("pid:", process.pid, '--', path, + new Date())  
    if (fs.existsSync) {
        //console.log("pid:", process.pid, '--', path, + new Date())
        return fs.existsSync(path);
    }
    if (syspath.existsSync) {
        //console.log("pid:", process.pid, '--', path, + new Date())
        return syspath.existsSync(path);
    }


}

var counter = 1000;

console.log("################")
var startTime = new Date();
for(var i = 0;i < counter;i++){
    exists("D:/OpenSource/fekit/lib/commands/pack.js")
}
console.log("修改后:",new Date() - startTime);

console.log("################")
startTime = new Date();
for(var i = 0; i < counter; i++){
_fileExistsWithCaseSync("D:/OpenSource/fekit/lib/commands/pack.js") 
}
console.log("修改前N级目录:",new Date() - startTime);


console.log("################")
startTime = new Date();
for(var i = 0; i < counter; i++){
_fileExistsWithCaseSync("D:/OpenSource/fekit/lib/commands/test/longer/pack.js") 
}
console.log("修改前N+2级目录:",new Date() - startTime);    

console.log("################")
startTime = new Date();
for(var i = 0; i < counter; i++){
_fileExistsWithCaseSync("D:/OpenSource/fekit/lib/commands/test/longer/test/longer/pack.js") 
}
console.log("修改前N+4级目录:",new Date() - startTime);    

2 问题由来

一开始一直使用的fekit 的一个比较老旧的版本,fekit@0.2.85;后来需要做一段touch开发,工程需要支持sass,于是升级了fekit至最新版。

也就是噩梦的开始,期间换个多个版本,效果都是一样。一个字,太慢了。刷一个页面要而二三十秒。

也尝试过去解决,希望可以找到一个临界版本,然后进行目录比较得到结果。事情没有能够顺利的进行,因为中间有好多版本没法使用(在windows上),所以根本没法这样获得结果。

也跟维护者进行过咨询和讨论,也没有得到一个较好的解决办法。

于是使用fekit 的-b -c 各种参数来进行性能提升,慢慢的强迫症有些消退,也适应了这种老牛拉破车的节奏。

3 转折点

然而,虽然我个人慢慢的适应了这个状态,但心里始终有个心结。后来其他部门的小伙伴,其他职位的小伙伴也慢慢的反映出此问题来了。

当邮件组再次出现这样的问题的时候,我也没有像一开始那么激动地要上去煽一把火,期待火大点总能燃起来。 只是默默的想着,我要有点时间,必搞搞此问题。

恰好来了个周末。

4 问题解决过程

4.1 搭环境

  • 使用nvm搞了两个node 环境,每一个node装一个fekit,一个最新版,一个0.2.85
  • 用同一个项目(自然是同一台机子),进行对比测试(还是老样子)
  • 然后是打log,尝试了node-inspector, 报错
  • 对比fekit pack,fekit min同理

4.2 代码对比分析 差异太大

  • 0.2.85是单进程,最新版是多进程
  • 各种模块有差异
  • 先改了改安装后的源代码,嵌入了一段代码,记录所有打包文件耗时的top5
  • 然后根据这个top5 分析代码特征
  • 发现都是一大片的require
  • 于是查看fekit 的CHANGE LOG,发现跟require相关的差异比较大的sass模块,替换上,没多大影响
  • 于是就懵逼了

4.3 目录对比,还是目录对比

  • 前面说到了,分析不下去了
  • 到这个份上了,只能硬比,硬不起来也要比
  • 生物学实验的氛围立马营造了起来
  • 0.2.85是单进程,最新版是多进程,首先把多进程搞成单进程
  • 尽量把最新版的模块都替换成参照物的0.2.85的模块
  • 然后是各种匹配,替换,各种失败
  • 不说了,都是泪
  • 看着看着发现某些代码差异比较大,看起来还io,满以为到头了,结果还是。。。
  • 大块的都搞完了,开始搞小块的了,代码已经被替换的不堪入目了
  • 然后是一个判断环境的一些,直接return一个值,没啥鸟用
  • 最后到了判断路径存在的,居然还是一个递归。。。。,替换到这儿,撞上了。

4.4 全是代码

4.4.1 一个记录TopN的小工具

topNManager = {
    n:5,
    maxN:[],
    infoMap:{},
    raceTop:function(timeDelta,path){
        var self = this;
        if(self.maxN.length < self.n){
            self.maxN.push(timeDelta);
            self.infoMap[timeDelta] = path;
        }else{
            var min = Math.min.apply(null,self.maxN);
            if(min < timeDelta){
                self.maxN.splice(self.maxN.indexOf(min),1,timeDelta);
                self.infoMap[timeDelta] = path;
            }
        }
    },
    print:function(){
        var self = this;
        utils.logger.log("耗时Top",self.n);
        self.maxN.sort(function(a,b){return b - a})
        self.maxN.forEach(function(value,index){
            utils.logger.log("耗时",value,", 路径:",self.infoMap[value]);
        })
    }
}

4.4.2 瓶颈点的代码

exists: function(path) {
  //return _fileExistsWithCaseSync(path);
  
  if (fs.existsSync) {
    return fs.existsSync(path);
  }
  if (syspath.existsSync) {
    return syspath.existsSync(path);
  }
  
},

  _fileExistsWithCaseSync = function(filepath) {
    console.log("pid:",process.pid,'--',filepath, + new Date())
    var dir, filenames;
    if (!fs.existsSync(filepath)) {
    return false;
    }
    dir = syspath.dirname(filepath);
    if (dir === '/' || dir === '.' || (/^\w:\\$/.test(dir))) {
    return true;
    }
    filenames = fs.readdirSync(dir);
    if (filenames.indexOf(syspath.basename(filepath)) === -1) {
    return false;
    }
    return _fileExistsWithCaseSync(dir);
};

说实话没看懂到底要干啥。

4.5 原因分析

说实话没看懂到底要干啥,github上抠代码,发现当时的提交日志是“开发时require区分大小写”。

看完又是懵逼了,代码里面没有看到大小写,整个工程中也就这一个文件用到了这个方法。

技术原因倒也简单,同步递归的效率太低,目录层级成了递归深度,同步递归的时候,这里用了多进程,CPU会都占满掉,然后会不断激活失活,其他进程时间片很少,机子也会出现卡死的状态。




最后更新: 2022年03月02日 03:32

原始链接: http://rawbin-.github.io/dev-tools/fekit/2016-01-11-fekit-performance/

× 赞赏这个人~
打赏二维码